#include "MonkeyContext.h"
#include "MonkeyExceptions.h"
#include "MonkeyObject.h"


static JSBool getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
{
    jsval idvp;
    
    JS_IdToValue(cx, id, &idvp);
    printf("getproperty %s\r\n", JS_EncodeString(cx, JS_ValueToString(cx, idvp)));
    
//    *vp = INT_TO_JSVAL(84);
    
    return JS_TRUE;
}


static JSBool getValue(JSContext *cx, uintN argc, jsval *vp)
{
    printf("getvalue %s\r\n", JS_EncodeString(cx, JS_ValueToString(cx, JS_ARGV(cx, vp)[0])));

    JS_SET_RVAL(cx, vp, INT_TO_JSVAL(42));
    return JS_TRUE;
}


static JSClass global_class = {
    "global", JSCLASS_GLOBAL_FLAGS,
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};

static void reportError(JSContext *cx, const char *message, JSErrorReport *report)
{
    MonkeyContext *ctx = (MonkeyContext *)JS_GetContextPrivate(cx);
    
    
    ctx->AddError(MonkeyError(report, message));
#if 1
    printf("%s:%u:%s:%s\n",
	(report->filename != NULL ? report->filename : "<no filename>"),
            (unsigned int) report->lineno,
            message,
           (report->linebuf != NULL ? report->linebuf : "<no code>"));
#endif
}


//
// Create a new MonkeyContext object for the specific runtime.
//
MonkeyContext::MonkeyContext(const MonkeyRuntime *rt)
{
    runtime = rt;
    
    //
    // Intialize the javascript context.
    //
    context = JS_NewContext(runtime->runtime, 8192);
    if (context == NULL)
        throw GenericException("Could not create JS context.");

    //
    // Set some default options.
    //
    JS_SetOptions(context, JSOPTION_VAROBJFIX | JSOPTION_JIT | JSOPTION_METHODJIT);
    JS_SetVersion(context, JSVERSION_LATEST);
    JS_SetErrorReporter(context, reportError);
    JS_SetContextPrivate(context, this);

    //
    // Create the global object for this context.
    //
    global = JS_NewCompartmentAndGlobalObject(context, &global_class, NULL);
    if (global == NULL)
        throw GenericException("Could not create JS global object.");
    if (!JS_InitStandardClasses(context, global))
        throw GenericException("Could not initialize standard classes.");

    JS_DefineFunction(context, global, "getValue", getValue, 1, 0);
    JS_DefineProperty(context, global, "digitalInput", INT_TO_JSVAL(5), getProperty, NULL, 0);
}


//
// Destroy the monkey context and the related javascript stuff.
//
MonkeyContext::~MonkeyContext()
{
	//
	// Delete all child objects.
	//
	while (children.size() > 0)
	{
		MonkeyObject *obj = *children.begin();
		
		children.remove(obj);
		delete obj;
	}

    JS_DestroyContext(context);
}


//
// Retrieve the Javascript context object.
//
JSContext *MonkeyContext::jsContext() const
{
	return context;
}


//
// Retrieve the Javascript global object.
//
JSObject *MonkeyContext::jsObject() const
{
	return global;
}


//
// Add a new object to this context, objects added at this level are global.
//
void MonkeyContext::AddObject(MonkeyObject *obj)
{
	obj->RegisterObject(this, global);
	children.push_back(obj);
}


//
// Compile and execute the given script code.
//
std::string MonkeyContext::EvaluateScript(const char *script)
{
    const char *filename = "noname";
    jsval rval;
    JSBool ok;


    //
    // Evaluate and run the script.
    //
    ClearErrors();
    ok = JS_EvaluateScript(context, global, script, (uintN)strlen(script), filename, 1, &rval);
    if (ok == JS_FALSE) {
        throw GenericException("Script could not be processed.");
    }
    else 
	    return std::string(JS_EncodeString(context, JS_ValueToString(context, rval)));
}


//
// Validate that the script code will compile.
//
bool MonkeyContext::ValidateScript(const char *script)
{
    const char *filename = "noname";
    JSObject *obj;
    
    
    //
    // Evaluate and run the script.
    //
    ClearErrors();
    obj = JS_CompileScript(context, global, script, (uintN)strlen(script), filename, 1);
    if (obj == NULL) {
        return false;
    }

    return true;
}

